<?php

define("MAXLINE",1000000);

class ImapUtils {
    var $host				= '';
    var $user				= 0;
    var $pass				= 1; // 设置是否启用debug功能
    var $port				= 143;
    var $protocol			= 'imap';
	var $novalidate_cert	= true ;
	var $mailboxies			= array();
	var $mbox				= null ;
	var $debug				= false ;
	var $types				= array("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER");
	var $encodings			= array("7BIT","8BIT","BINARY","BASE64","QUOTED-PRINTABLE","OTHER") ;
	var $attachment_path	= "" ;
	var $file_name_encoding	= "gbk" ;
	var $text_encoding		= "utf-8" ;
	var $file_index			= 0 ;
	var $save_file_by_date	= 1 ;
	var $save_eml			= 1 ;
	var $split				= "\r\n\r\n" ;
		
    //---------------
    // 基础操作
    //---------------
 
    //构造函数
    function ImapUtils($host, $user, $pass){
        $this->host	= trim($host);
        $this->user	= trim($user);
        $this->pass	= trim($pass);
    }
    
	function get_mailboxies(){
		return $this->mailboxies ;
	}

	function open($folder){
		imap_timeout(IMAP_OPENTIMEOUT,1200);
		imap_timeout(IMAP_READTIMEOUT,1200);
		imap_timeout(IMAP_WRITETIMEOUT,1200);
		imap_timeout(IMAP_CLOSETIMEOUT,1200);

		$host = "{".$this->host.":".$this->port."/".$this->protocol .($this->novalidate_cert ? "/novalidate-cert" : "") ."}" . $folder ;
		$this->mbox = imap_open($host, $this->user, $this->pass) or die("can't connect: " . imap_last_error());
		$this->list_mailboxies();
	}

	function list_mailboxies(){
		if(empty($this->mailboxies)){
			$list = imap_list($this->mbox, "{".$this->host.":143}", "*");
			if (is_array($list)) {
				reset($list);
				while(list($key, $val) = each($list)) {
					array_push($this->mailboxies, imap_utf7_decode($val));
				}
			}
			unset($list);
		}
	}

	function get_msg_count(){
		return imap_num_msg($this->mbox);
	}

	function get_recent_msg_count(){
		return imap_num_recent($this->mbox);
	}

	function delete_msg($i){
		imap_delete($this->mbox,$i);
	}

	function set_flag_seen($i){
		return $this->set_flag($i,"\\Seen");
	}

	function set_flag($i,$flag){
		$sequence = "$i,$i";
		return imap_setflag_full($this->mbox, $sequence, $flag);
	}

	function get_msg($i,$header_only=true){
		$this->show_time("begin");
		$headers = $this->object_2_array(imap_headerinfo($this->mbox, $i, 80, 80)) ;
		$this->show_time("finish fetch header info");
		$headers = $this->do_mime_decode($headers) ;
		$this->show_time("finish header mime decode");

		$msg = array();
		$msg["headers"] = $headers; 
		if(array_key_exists("message_id",$headers)){
			$msg["message_id"] = $headers["message_id"];
		}else{
			$msg["message_id"] = md5(json_encode($headers));
		}
		if($header_only){
			return $msg ;
		}

		$this->show_time("finish fetch message_id");

		$struct = $this->object_2_array(imap_fetchstructure($this->mbox,$i)) ;
		$msg["structure"] = $struct ;
		$msg["attachments"] = array();
		$msg["images"] = array();
		$msg["messages"] = array();

		$this->show_time("finish fetch structure");

//		$this->debug($i);
		if(array_key_exists("parts",$struct) && count($struct["parts"]) > 1){
			$index = 1 ;
			foreach ($struct["parts"] as $part){
				$this->parse_part($msg,imap_fetchbody($this->mbox,$i,$index++),$part);
			}
		}else{
			$this->parse_part($msg,imap_fetchbody($this->mbox,$i,1),$struct);
		}
		return $msg ;
	}

	function parse_part(&$msg,$body,$struct){
		$this->show_time("begin parse body ". md5($body));
		$boundary = $this->get_boundary($struct);
		$message_id = $msg["message_id"];
		$middle_path = $this->get_date_folder($msg) ;

		if($boundary != ""){
			$matrix = explode($boundary,$body);
			$size = count($struct["parts"]) ;
			if($struct["parts"] && $size > 1){
				$index = 1 ;
				foreach ($struct["parts"] as $part){
					$this->parse_part($msg,$matrix[$index++],$part);
				}
				return ;
			}
		}

		$type = $struct["type"];
		if($type == 0){
			$subtype = $struct["subtype"] ;
			if($subtype == "PLAIN" || $subtype == "HTML"){
				$matrix = $this->body_split($body);
				$body_headers = array() ;
				if(count($matrix) > 1){
					$body_headers = $this->parse_content($matrix[0]) ;
					$body = trim($matrix[1]);
				}

				$encode = $struct["encoding"] ;
				$string = $this->mime_decode($body,$encode);
				$charset = strtolower($this->get_parameter($struct,"charset"));
				if(!empty($charset) && $charset != $this->text_encoding){
					$string = @iconv($charset , $this->text_encoding . "//IGNORE",$string); 
				}
				$key = $this->types[$type] . "/" . $subtype ;
				$msg[$key] = array("body"=>$string,"headers"=>json_encode($body_headers)) ;
			}else{
				$this->debug("unknow subtype $subtype");
			}
			return ;
		}
		
		$properties = array_keys($struct) ;
		
		if($struct["type"] == 5){ // image
			$matrix = $this->body_split($body);
			$count = count($matrix);
			$image = array();

			$image_body = "" ;
			if($count >= 2){
				$image = array_merge($image,$this->parse_content($matrix[0]));
				$image_body = $matrix[1] ;
			}else if($count == 1){
				$image_body = $matrix[0] ;
			}
			if(!empty($image_body)){
				$enc = $this->file_name_encoding ; 
			//	print json_encode($struct);
				$image["bytes"] = mget($struct,"bytes") ;
				$image["struct"] = json_encode($struct) ;
				$image["name"] = $this->get_parameter($struct,"name"); 
				$image["content_type"] = $this->types[5] . "/" . $struct["subtype"] ;
				$image["filename"] = $this->imap_mime_decode($this->get_parameter($struct,"filename"),$enc) ;
				$image["disposition"] = mget($struct,"disposition") ;
				$image["content_id"] = mget($struct,"content_id") ;
				$image["id"] = mget($struct,"content_id") ;
				
				if(!empty($this->attachment_path)){
					$encoding = $struct["encoding"] ;
					// $string = $this->mime_decode($image_body,$encoding);
					$save_path = $this->attachment_path . "/" . $middle_path ;
					$save_name = $this->get_file_name($message_id,$image["name"]);
					$save_file = $save_path . $save_name ;
					mkdirs($save_path);
					$this->base64_to_jpeg($image_body,$save_file);
					$image["save_path"] = $save_path ;
					$image["save_name"] = $save_name ;
					$image["save_file"] = $save_file ;
				}
				array_push($msg["images"],$image) ;
			}else{
				$this->debug("can not find image_body at: " . $body);
			}
			return ;
		}

		if($struct["type"] == 2){	// message
		//	array_push($msg["messages"],json_encode($struct));
		//	array_push($msg["messages"],json_encode($body));

			$subject = $this->get_subject_from_eml_body($body);
		//	$this->debug($subject);

			$enc = $this->file_name_encoding ;
			$attachment = array();
			$attachment["bytes"] = mget($struct,"bytes") ;
			$attachment["struct"] = json_encode($struct) ;
			$attachment["content_type"] = $this->types[$struct["type"]] . "/" . $struct["subtype"] ;
			$attachment["filename"] = $subject . ".eml" ;
			$attachment["name"] = $attachment["filename"] ; 
			$attachment["disposition"] = mget($struct,"disposition") ;
			$attachment["content_id"] = mget($struct,"content_id","") ;

			$this->debug($this->attachment_path);

			if(!empty($this->attachment_path)){
				$encoding = $struct["encoding"] ;
			//	$string = $this->mime_decode($body,$encoding);
				$string = $body ;
			
				$save_path = $this->attachment_path . "/" . $middle_path ;
				$save_name = $this->get_file_name($message_id,"a.eml");
				$save_file = $save_path . $save_name ;
				mkdirs($save_path);

				rewrite_file($save_file,$string,$this->text_encoding,false);
				
		//		$this->debug($save_file);

				$attachment["save_path"] = $save_path ;
				$attachment["save_name"] = $save_name ;
				$attachment["save_file"] = $save_file ;
			}
			array_push($msg["attachments"],$attachment) ;

			return ;
		}

		if(array_key_exists("disposition",$struct)){
			if($struct["disposition"] == "attachment"){
				$enc = $this->file_name_encoding ;
				$attachment = array();
				$attachment["bytes"] = mget($struct,"bytes") ;
				$attachment["struct"] = json_encode($struct) ;
				$attachment["content_type"] = $this->types[$struct["type"]] . "/" . $struct["subtype"] ;
				$attachment["filename"] = $this->imap_mime_decode($this->get_parameter($struct,"filename"),$enc) ;
				$attachment["name"] = $this->imap_mime_decode($this->get_parameter($struct,"name"),$enc); 
				$attachment["disposition"] = mget($struct,"disposition") ;
				$attachment["content_id"] = mget($struct,"content_id") ;

				if(!empty($this->attachment_path)){
					$encoding = $struct["encoding"] ;
					$string = $this->mime_decode($body,$encoding);
				
					$save_path = $this->attachment_path . "/" . $middle_path ;
					$save_name = $this->get_file_name($message_id,$attachment["name"]);
					$save_file = $save_path . $save_name ;
					mkdirs($save_path);

					rewrite_file($save_file,$string,$this->text_encoding,false);
					$attachment["save_path"] = $save_path ;
					$attachment["save_name"] = $save_name ;
					$attachment["save_file"] = $save_file ;
				}
				array_push($msg["attachments"],$attachment) ;
			}else{
				$this->debug("unknow disposition: " . $struct->disposition);
			}
			return ;
		}

		$msg["unfinish"] = 1 ;
		/*
		$this->debug("unknow body part:");
		$this->debug(json_encode($struct));
		$this->debug($type);
		$this->debug(json_encode($struct));
		$this->debug($body);
		*/
	}

	function get_date_folder($msg){
		if($this->save_file_by_date){
			$head = $this->get_msg_header($msg,array("Date","Received","udate","MailDate"));
			$time = string_to_date($head);
			$date = date("Y-m-d",$time) ;
			return $date . "/" ;
		}
		return "";
	}

	function get_msg_header($msg,$keys){
		for($i=0;$i<count($keys);$i++){
			$key = $keys[$i];
			if(array_key_exists($key,$msg["headers"])){
				$header = $msg["headers"][$key] ;
				if(!empty($header)){
					return $header ;
				}
			}
		}
		return "";
	}

	function do_mime_decode($object){
		$keys = array_keys($object) ;
		for($t=0;$t<count($keys);$t++){
			$key = $keys[$t];
			$value = $object[$key];

			if(empty($value)){
				continue ;
			}
			if(is_array($value)){
				$object[$key] = $this->do_mime_decode($value);
				continue ;
			}

			$value = $this->imap_mime_decode($value);
			$object[$key] = $value ;
		}
		return $object ;
	}

	function imap_mime_decode($string,$target_charset='utf-8'){
		if(strpos($string,"?=") != false){
			$result = "";
			$matrix = preg_split("(\r|\n|\t| )",$string);
			for($i=0;$i<count($matrix);$i++){
				$result .= $this->imap_mime_decode_one($matrix[$i]);
			}
			return $result ;
		}else{
			return $this->imap_mime_decode_one($string);
		}
	}

	function imap_mime_decode_one($string,$target_charset='utf-8'){
		$elements = imap_mime_header_decode($string);
		$result = "";
		for($i=0;$i<count($elements);$i++){
			$charset = $elements[$i]->charset ;
			if($charset != "default" && $charset != $target_charset){
				// $this->debug("===charset:".$charset.",string:$string");
				$result .= @iconv($charset , $target_charset."//IGNORE",$elements[$i]->text); 
				$error = error_get_last(); 
				if(strpos($error["message"],"Detected an incomplete multibyte character in input string") != false){
					// print "get error";
				}
			}else{
				$result .= $elements[$i]->text ;
			}
		}
		return $result ;
	}

	function mime_decode($string,$encoding){
		if($encoding == 0){ // 7bit
			// $string = imap_7bit($string);
			// $string = $this->decode7bit($string);
		}else if($encoding == 1){ // 8bit
			$string = imap_8bit($string);
		}else if($encoding == 2){ // binary
			$string = imap_binary($string);
		}else if($encoding == 3){ // base64
			$string = base64_decode($string);
		}else if($encoding == 4){ // QUOTED-PRINTABLE
			$string = imap_qprint($string);
		}else if($encoding == 5){ // OTHER
			
		}
		return $string ;
	}

	function decode7bit($text){
		$ret = '';
		$data = str_split(pack('H*',$text));

		$mask = 0xFF;
		$shift = 0;
		$carry = 0;
		foreach ($data as $char) {
			if ($shift == 7) {
				$ret .= chr($carry);
				$carry = 0;
				$shift = 0;
			}
			$a = ($mask >> ($shift+1)) & 0xFF;
			$b = $a ^ 0xFF;

			$digit = ($carry) | ((ord($char) & $a) << ($shift)) & 0xFF;
			$carry = (ord($char) & $b) >> (7-$shift);
			$ret .= chr($digit);
			$shift++;
		}
		if ($carry){
			$ret .= chr($carry);
		}
		return $ret;
	}

	function buildparts($struct, $pno = "") {
		$parttypes = $this->types ;
		switch ($struct["type"]):
			case 1:
				$r = array (); $i = 1;
				foreach ($struct["parts"] as $part){
					$r[] = $this->buildparts ($part, $pno.".".$i++);
				}
				return implode (",", $r);
			case 2: 
				return $this->buildparts($struct["parts"][0], $pno);
			default:
				return $parttypes[$struct["type"]]."/".strtolower($struct["subtype"]);
		endswitch;
	}

	function get_mime_type(&$structure){ 
		if($structure["subtype"]) { 
			return $this->types[(int) $structure["type"]] . '/' . $structure["subtype"]; 
		} 
		return "TEXT/PLAIN"; 
	} 

	function get_boundary($struct){
		return $this->get_parameter($struct,"boundary");
	}

	function get_parameter($struct,$name){
		if(array_key_exists("ifparameters",$struct)){
			if(array_key_exists("parameters",$struct) && is_array($struct["parameters"])){
				for($i=0;$i<count($struct["parameters"]);$i++){
					$parameter = $struct["parameters"][$i];
					if($parameter["attribute"] == $name){
						return $parameter["value"];
					}
				}
			}
		}
		return "";
	}

	function init_array($msg,$key){
		if(array_key_exists($key,$msg)){
			return ;
		}
		$msg[$key] = array();
	}

	function get_subject_from_eml_body($string){
		$temp = $this->body_split($string);
		if(!empty($temp) && is_array($temp) && count($temp) > 1){
			$matrix = $this->parse_content($temp[0]);
	//		$this->debug(json_encode($matrix));
			$subject = mget($matrix,"Subject");
			if(!empty($subject)){
				return $this->imap_mime_decode_one($subject);
			}
		}
		return "" ;
	}
	
	function body_split($string){
		$string = trim($string) ;
		$line = $this->first_line($string);

		$temp = array();

		if($this->is_body_head($line)){
			$matrix = explode($this->split,$string);
			foreach($matrix as $part){
				$part = trim($part) ;
				if(empty($part)){
					continue ;
				}
				array_push($temp,$part);
			}
		}else{
			array_push($temp,$string) ;
		}
		return $temp ;
	}

	function parse_content($string){
		$string = str_replace("\n\t","\n",$string);
		$matrix = explode("\r\n",$string);
		$result = array();
		for($i=0;$i<count($matrix);$i++){
			$result = array_merge($result,$this->split_key_value($matrix[$i]));
		}
		return $result ;
	}

	function split_key_value($string){
		$index = strpos($string,":");
		if($index == false){
			$index = strpos($string,"=");
		}
		$len = $index ;
		$key = trim(substr($string,0,$len));
		$value = trim(substr($string,$len+1));
		return array($key=>$value) ;
	}

	function first_line($string){
		$index = strpos($string, "\r\n");
		if($index > 0){
			return substr($string,0,$index);
		}
		return $string ;
	}

	function is_body_head($line){
		$regexp = "/^(([\w\d\-\_])+( )?(\:))/i";
		$flag = preg_match($regexp,$line);
		return $flag != false ;
	}

	function close(){
		imap_expunge($this->mbox);
		imap_close($this->mbox);
	}

	function base64_to_jpeg($string, $outputfile ) { 
		$ifp = fopen($outputfile, "wb" ); 
		fwrite( $ifp, base64_decode( $string ) ); 
		fclose( $ifp ); 
		return( $outputfile ); 
	} 

	function object_2_array($object){
		if(is_array($object)){
			return $object ;
		}
		$temp = json_encode($object);
		return json_decode($temp,true);
	}

	function get_file_name($message_id,$source_name){
		$string = md5($message_id);
		return $string . "_" . ($this->file_index++) . "." . $this->get_file_type($source_name);
	}

	function get_file_type($name){
		$matrix = explode(".",$name);
		return $matrix[count($matrix) - 1];
	}

	function debug($info,$obj=0){
		if(!$obj){
			if($this->debug){
				$string = "--------------------------------------------------------------------------------------";
				print "<br>";
				print $string ;
				print "<br>";
				print str_replace("\r\n","<br>",$info) ;
				print "<br>";
				print $string ;
			}
		}
	}

	function show_time($title){
		if($this->debug){
			$time = current_time();
			print "$time : $title <br>";
		}
	}
}

?>